package operations;

import java.util.LinkedList;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;



import model.Filter;
import util.GAConfig;
import util.Randomness;

public class ValuesGenerator {
    public static float GEN_BASIC_VALUES = .40f;
    public static float LIMITED_VALUES = .50f;

	public static Object generatePrimitiveValue(Class<?> c){
		if(Filter.isPrimitiveOrStringType(c)){
			 if( Boolean.class.equals(Filter.primitiveAndStringType.get(c)))
					return  getBoolean(.5f);
			 if( Byte.class.equals(Filter.primitiveAndStringType.get(c)))
					return  getByteValue();
			 if( Integer.class.equals(Filter.primitiveAndStringType.get(c)))
					return  getIntValue();
			 if( Long.class.equals(Filter.primitiveAndStringType.get(c)))
					return  getLongValue();
			 if( Float.class.equals(Filter.primitiveAndStringType.get(c)))
					return  getFloatValue();
			 if( Double.class.equals(Filter.primitiveAndStringType.get(c)))
					return getDoubleValue();			 
			 if( Character.class.equals(Filter.primitiveAndStringType.get(c)))
				 return getCharacter();
			 if( c.equals(String.class))
					return  getStringValue();
			 if(Short.class.equals(Filter.primitiveAndStringType.get(c)))
					 return  getShortValue();
			 else	
					System.err.print("ValuesGenerator.generatePrimitiveValue: unknowen type object "+ c.getName());
 
		}
		else{
			System.err.print("ValueGeneratogeneratePrimitiveValue: unknowen type object "+ c.getName());
		}
		return null;
	}
	private static short getShortValue() {
	    short n=0; 	
	    float nextFloat = Randomness.random.nextFloat();
		if(nextFloat < GEN_BASIC_VALUES)
		switch(Randomness.random.nextInt(5)) {
		case 0:
			return 0;
		case 1:
			return 1;
		case 2:
			return -1;
		case 3:
			return Short.MAX_VALUE;
		case 4:
			return Short.MIN_VALUE;
		}
		if(nextFloat < GEN_BASIC_VALUES + LIMITED_VALUES) 
			n= (short)( Randomness.random.nextInt(21) - 10);
		else
			n=(short) (Randomness.random.nextInt(java.lang.Short.MAX_VALUE - java.lang.Short.MIN_VALUE) - java.lang.Short.MIN_VALUE);
		
		return (short) n;

	}

	public static int getRandomIntBetween(int min, int max) {
		
		long range       = (long)max - (long)min + 1;
		long fraction    = (long)(range * Randomness.random.nextDouble());
	    int randomNumber = (int)(fraction + min);
		return randomNumber;
	}
	public static double getRandomRealBetween(int min, int max) {

		double n = min	+ Randomness.random.nextInt(1000 * (max - min) + 1)	/ 1000.0;
		return n;
	}
	public static Object modifyRecommendedValue(Object orgValue,Object recValue,Class<?> vType) {
		if (recValue==null)
				return  generatePrimitiveValue(vType);

		if(Filter.primitiveAndStringType.get(vType)== Boolean.class) 
			return generatePrimitiveValue(vType);
		if(Filter.primitiveAndStringType.get(vType)== Byte.class){
		//	byte newValue = (byte) (((Byte)orgValue)+((Byte)recValue));
			byte newValue = (byte) ((Byte)recValue);
			if(newValue>=Byte.MAX_VALUE)
				return (newValue+ ValuesGenerator.getRandomIntBetween(Byte.MIN_VALUE, 0));
			if(newValue<=Byte.MIN_VALUE)
				return (newValue+ ValuesGenerator.getRandomIntBetween(1, Byte.MAX_VALUE));

			return newValue + ValuesGenerator.getRandomIntBetween(-1, 1);
		}

		if(Filter.primitiveAndStringType.get(vType)== Integer.class){
			int newValue = (Integer) orgValue +(Integer) recValue;
			return newValue + ValuesGenerator.getRandomIntBetween(-1, 1);
		}
		if(Filter.primitiveAndStringType.get(vType)== Long.class){
			Long newValue = (Long) orgValue +(Long) recValue;
			return newValue + ValuesGenerator.getRandomIntBetween(-1, 1);
		}

		if(Filter.primitiveAndStringType.get(vType)== Double.class){
			Double newValue = (Double) orgValue +(Double) recValue;
			return newValue + ValuesGenerator.getRandomIntBetween(-1, 1);
		}
		if(Filter.primitiveAndStringType.get(vType)== Float.class){
			Float newValue = (Float) orgValue +(Float) recValue;
			return newValue + ValuesGenerator.getRandomIntBetween(-1, 1);
		}

		if(Filter.primitiveAndStringType.get(vType)== java.lang.Character.class||recValue== char.class){ 
			if(ValuesGenerator.getBoolean(0.5f))
				return recValue;
			else
				return (Object) (((Character) recValue ) + ValuesGenerator.getRandomIntBetween(-1, 1));
		}
		if(Filter.primitiveAndStringType.get(vType)== String.class){	
//			if(orgValue!=null)
//			 if(((String)orgValue).toString().length()>GAConfig.String_length)
//				 System.out.println("modifyRecommendedValue String length(): "+((String)orgValue).toString().length());
			
			
			Object newValue=ValuesGenerator.stringValueMuteated(orgValue,(recValue.toString().trim()));
			if( newValue!=null)
				return newValue;
		}
		else{
			System.err.println("Error: ** ValuesGenerator.modifyActualValue **"+ vType);
			System.exit(1);
		}
		
		return null;
		
	}
	 public static Object stringValueMuteated(Object actuallValue,Object pSuggestValue){
		 String aValue=(String) actuallValue;
		 String pValue=(String) pSuggestValue;
//		 if(aValue!=null && aValue.length()>GAConfig.String_length)
//			 System.out.println("ValueGenerato.stringValueMuteated aValue.length()-1: "+ aValue.length());
		
			 if(actuallValue==null){
		//		 System.out.println("ValuesGenerator.string ValueMuteated: actuallValue=null");
				 aValue="";
			 }
			 if(pValue.trim().length()==0 &&aValue.trim().length()==0){
				 return ValuesGenerator.getStringValue();
			 }
			 if(pValue.equals(aValue))
				 pValue=""+ValuesGenerator.getCharacter();
				/** probability to add a char before the selected char */
				 float probAddChar = 0.5f;

				/** probability to remove the selected char */
				 float probRemoveChar = 0.2f;

				/**  probability to modify the selected char  */
				 float probModifyChar = 0.3f;
				 String newValue=null;
			 if(pValue==null||pValue.equals("null"))
				 return newValue;
			 else
				 newValue=pValue.toString();
			 final boolean isAddable = aValue.length() <GAConfig.String_length;
			 final boolean isModifiable = aValue.length() > 0  ;//&& (methodsRecommend.containsKey("init"));

			final boolean isRemovable = aValue.length()>0;
			 
			 int choice;
			 if(isModifiable && isRemovable) {
				float c = ((Float)ValuesGenerator.getFloatValue()) * (probAddChar + probRemoveChar + probModifyChar);
				if(c < probAddChar && isAddable) choice = 0;
				else if(c < probAddChar + probModifyChar) choice = 1;
				else choice = 2;
			} else if(isModifiable) {
				float c = ((Float)ValuesGenerator.getFloatValue()) * (probAddChar + probModifyChar);
				if(c < probAddChar && isAddable) choice = 0;
				else choice = 1;
			} else if(isRemovable) {
				float c = ((Float)ValuesGenerator.getFloatValue())* (probAddChar + probRemoveChar);
				if(c < probAddChar&& isAddable) choice = 0;
				else choice = 2;
			} else choice = 0;
			 int postition=0;
			 if(aValue.length()!=0)
				 	postition=Randomness.random.nextInt(aValue.length());
			 
			if(choice==0)// add a suggested value to the string
				newValue = new StringBuffer(aValue).insert(postition, pValue).toString();
			if(choice==1)//modify a char
				newValue = aValue.substring(0,postition)+ pValue  +aValue.substring(postition+1);
			if(choice==2)//remove a char  str.substring(0, idx) + str.substring(idx + 1);
				newValue = aValue.substring(0,postition)+ aValue.substring(postition+1);
	
//			 if(newValue.length()>GAConfig.String_length)
//				 System.out.println("ValueGenerato.stringValueMuteated newValue.length()-2: "+ newValue.length());
		
			return newValue;
		 }

	public static Object modifyPrimitiveValue(Class<?> c, Object oldValue){
		if(Filter.isPrimitiveOrStringType(c)){
			if(oldValue == null)
				return generatePrimitiveValue(c);

			 if( Boolean.class.equals(Filter.primitiveAndStringType.get(c)))
				 return !((Boolean)oldValue);
			 
				if(Byte.class.equals(Filter.primitiveAndStringType.get(c))){
					Byte v=new Byte(oldValue.toString());
					if(v==Byte.MAX_VALUE)
						return (v+ ValuesGenerator.getRandomIntBetween(Byte.MIN_VALUE, 0));
					if(v==Byte.MIN_VALUE)
						return (v+ ValuesGenerator.getRandomIntBetween(1, Byte.MAX_VALUE));
					
					return (v+ ValuesGenerator.getRandomIntBetween(-1, 1));
				}

			 if( Integer.class.equals(Filter.primitiveAndStringType.get(c))){
				 if (oldValue.equals(Integer.MIN_VALUE)) 
					 return generatePrimitiveValue(c);
				 if (oldValue.equals(Integer.MAX_VALUE)) 
					 return generatePrimitiveValue(c);
			 
					return  (new Integer(oldValue.toString())) +getRandomIntBetween(-10,10) ;
			 }
			 if( Long.class.equals(Filter.primitiveAndStringType.get(c)))
					return  (Long)oldValue+getRandomIntBetween(-10,10);
			 if( Float.class.equals(Filter.primitiveAndStringType.get(c))){
				 if (oldValue.equals(Float.POSITIVE_INFINITY)) 
				 		return	getFloatValue();
			       else if (oldValue.equals(Float.NEGATIVE_INFINITY)) 
			    	   return getFloatValue();
			    	    else if (oldValue.equals(Float.NaN)) 
			    	    	return getFloatValue();
				 return  (Float)oldValue+(float)getRandomRealBetween(-10,10);
			 }
			 if( Double.class.equals(Filter.primitiveAndStringType.get(c))){
				 if (oldValue.equals(Double.POSITIVE_INFINITY)) 
					 		return	getDoubleValue();
				       else if (oldValue.equals(Double.NEGATIVE_INFINITY)) 
				    	   return getDoubleValue();
				    	    else if (oldValue.equals(Double.NaN)) 
				    	    	return getDoubleValue();
				return  (new Double(oldValue.toString()))+getRandomRealBetween(-10,10);
			 }
				 		 
			 if( Character.class.equals(Filter.primitiveAndStringType.get(c)))
				 return getCharacter();
			 if( c.equals(String.class))
					return  modifyStringValue(oldValue);
		}
		else{
			System.err.print("ValueGeneratogeneratePrimitiveValue: unknowen type object "+ c.getName());
			System.exit(1);
		}
		return null;
	}
	private static Object modifyStringValue(Object oldValue){
	    char[] values = oldValue.toString().toCharArray();	 
//		 if(values.length>GAConfig.String_length)
//			 System.out.println("ValueGenerato.modifyStringValue String length()-1: "+ values.length);

		final int lBytes = values.length;
//		if(lBytes==0)
//			return oldValue;
		char[] newBytes;
		int choice=( (lBytes == 0))? 1: Randomness.random.nextInt(3);
		if(lBytes>GAConfig.String_length)
			choice=2;
		if(lBytes==GAConfig.String_length)
			choice=ValuesGenerator.getRandomIntBetween(2,3);

		switch(choice ) {
		case 1: // add one or more character(s)
		{
			int howMany = Randomness.random.nextInt(GAConfig.String_length-lBytes)+1;
			 ArrayList<Integer> nPos = new ArrayList<Integer>();
			while(nPos.size() < howMany){
				int indx=Randomness.random.nextInt(lBytes + howMany);
				if(!nPos.contains(indx))
					nPos.add(indx);
			}
			Collections.sort(nPos);
			newBytes = new char[lBytes + nPos.size()];

			int j = 0;
			for (int i = 0; i < newBytes.length; i++) {
				if( nPos.contains(i))
					newBytes[i] = (Character)  getCharacter();
				else
					newBytes[i] = values[j++];
			}

			break;
		}
		case 2: // remove a character
		{
			int howMany = 1 + (lBytes == 1 ? 0 : Randomness.random.nextInt(lBytes-1));
			 ArrayList<Integer> nPos = new ArrayList<Integer>();
				while(nPos.size() < howMany){
					int indx=Randomness.random.nextInt(lBytes);
					if(!nPos.contains(indx))
						nPos.add(indx);
				}

				Collections.sort(nPos);
				newBytes = new char[lBytes - nPos.size()];


			int j = 0;
			for (int i = 0; i < values.length; i++) {
				if( nPos.contains(i))
					continue;
				else
					newBytes[j++] = values[i];
			}
			break;
		}
		default: // change a character
		{
			newBytes = values;

			int howMany = 1 + (lBytes == 1 ? 0 : Randomness.random.nextInt(lBytes-1));
			List<Integer> nPos = new LinkedList<Integer>();
			while(nPos.size() < howMany)
				nPos.add(Randomness.random.nextInt(lBytes));

			for (Integer i : nPos) {				
				newBytes[i] = (Character) getCharacter();
			}
		}
		}
//	if(newBytes.length>GAConfig.String_length)
//		System.out.println("ValueGenerator.modifyStringValue:-2 "+ newBytes.length);
		return new String(newBytes);
	}
	public static boolean getBoolean(float prob){
		return (Randomness.random.nextFloat() <= prob);
	}
	private static byte getByteValue() {
		float nextFloat = Randomness.random.nextFloat();
		byte n=0;
		if(nextFloat < GEN_BASIC_VALUES) 
		switch(Randomness.random.nextInt(5)) {
		case 0:
			n=0;;
		case 1:
			n= 1;
		case 2:
			n= -1;
		case 3:
			n= Byte.MAX_VALUE;
		case 4:
			n= Byte.MIN_VALUE;
		}

		n= (byte) getRandomIntBetween(Byte.MIN_VALUE,Byte.MAX_VALUE);
		return n;

	}

	public static Object getIntValue() {
		float nextFloat = Randomness.random.nextFloat();
		int n=0;
		if(nextFloat < GEN_BASIC_VALUES){
			switch(Randomness.random.nextInt(5)) {
				case 0:
					n= 0;
					break;
				case 1:
					n= 1;break;
				case 2:
					n= -1;break;
				case 3:
					n= Integer.MAX_VALUE;break;
				case 4:
					n= Integer.MIN_VALUE;break;
			}
		}else
			if(nextFloat < GEN_BASIC_VALUES + LIMITED_VALUES) 
				n= Randomness.random.nextInt(21) - 10;
			else
				n= Randomness.random.nextInt();
		
		return n;
	}
	private static Object getLongValue() {
		float nextFloat = Randomness.random.nextFloat();
		long n=0;
		if(nextFloat < GEN_BASIC_VALUES) switch(Randomness.random.nextInt(5)) {
		case 0:
			n= 0L;
		case 1:
			n= 1L;
		case 2:
			n= -1L;
		case 3:
			n= Long.MAX_VALUE;
		case 4:
			n= Long.MIN_VALUE;
		}

		if(nextFloat < GEN_BASIC_VALUES + LIMITED_VALUES) 
			n= (long) Randomness.random.nextInt(21) - 10;
		else
			n= Randomness.random.nextLong();
		
		return n;
		
	}

	public static Object getStringValue() {

		    if (getBoolean(GAConfig.SET_TO_NULL)) 
		    	return null;
		    String s = "";
		    int addMoreChars = Randomness.random.nextInt(100);
		    while ((addMoreChars<60) && (s.length() < GAConfig.String_length)) {
		      s +=  ""+getCharacter();
		      addMoreChars = Randomness.random.nextInt(70);;
		    }
		    if(s.length()>GAConfig.String_length)
		    	System.out.println("VauesGenerator.getStringValue " + s.length());
		    return s;
		}
	 public static Object getFloatValue() {

		    float n=0; 	
		    float nextFloat = Randomness.random.nextFloat();

			if(nextFloat < GEN_BASIC_VALUES) switch(Randomness.random.nextInt(10)) {
			case 0:
				n=  0.0f;break;
			case 1:
				n=  1.0f;break;
			case 2:
				n=  -1.0f;break;
			case 3:
				n=  Float.MAX_VALUE;break;
			case 4:
				n=   -1 * Float.MAX_VALUE;break;
			case 5:
				n=   Float.MIN_VALUE; break;
			case 6:
				n=  -1 * Float.MIN_VALUE;break;
			case 7:
				n=  Float.POSITIVE_INFINITY;break;
			case 8:
				n=  Float.NEGATIVE_INFINITY;break;
			case 9:
				n= Float.NaN;break;
			}
			else
				n=(float)getRandomRealBetween(-10,10);
				return n;
		  }
	 
	 public  static Object getDoubleValue() {

		    float nextFloat = Randomness.random.nextFloat();

			if(nextFloat < GEN_BASIC_VALUES)
				switch(Randomness.random.nextInt(10)) {
					case 0:
						return  0.0d;
					case 1:
						return  1.0d;
					case 2:
						return  -1.0d;
					case 3:
						return  Double.MAX_VALUE;
					case 4:
						return  (-1 * Double.MAX_VALUE);
					case 5:
						return  Double.MIN_VALUE; // number closest to 0+
					case 6:
						return  (-1 * Double.MIN_VALUE); // number closest to 0-
					case 7:
						return  Double.POSITIVE_INFINITY;
					case 8:
						return  Double.NEGATIVE_INFINITY;
					case 9:
						return  Double.NaN;
					}

			if(nextFloat < GEN_BASIC_VALUES + LIMITED_VALUES)
				return  (Randomness.random.nextBoolean() ? 1 : -1) * Randomness.random.nextDouble();

			return  (Randomness.random.nextBoolean() ? 1 : -1) * Randomness.random.nextDouble() * Double.MAX_VALUE;
		
			}
		private static final char[] SPECIAL_CHARACTERS = {
			' ','>','<',
			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
			'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
			'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
			'.', ',', ';', ':', '-',  '+', '?', '!',
			 '$',  '%', '&', '/', '(', ')', '=', '^', '#',  '[', ']', '{', '}'};

		public static Object getCharacter() {
				return SPECIAL_CHARACTERS[Randomness.random.nextInt(SPECIAL_CHARACTERS.length)];
		}

		 public static String toCodeString(Class<?> valueClass, Object value) {

				 
			    if (value == null) {
			    	return ("null");
			      }
			    if(value.equals("null"))
			    	 return ("null");
			    if(!Filter.isPrimitiveOrStringType(valueClass))
			    	return( value.toString());

			 if(valueClass.equals(String.class))
	    		 return("\"" + value.toString().trim() + "\"" );
				else
					 if( Character.class.equals(Filter.primitiveAndStringType.get(valueClass)))
					      if (value.equals(' '))
					          return " ' '";
					      else
					    	  return("\'" + value + "\'" );
					else
						if (double.class.equals(valueClass)) {
						      Double d = (Double) value;
						      String rep = null;
						      if (d.isNaN()) {
						    	  rep ="Double.NaN";
						      } else if (d == Double.POSITIVE_INFINITY) {
						    	  rep ="Double.POSITIVE_INFINITY";
						      } else if (d == Double.NEGATIVE_INFINITY) {
						    	  rep = "Double.NEGATIVE_INFINITY";
						      } else {
						    	  rep =d.toString();
						      }
						      return(rep);
						}
						else if (float.class.equals(valueClass)) {
						      Float d = (Float) value;
						      String rep = null;
						      if (d.isNaN()) {
						        return "Float.NaN";
						      } else if (d == Float.POSITIVE_INFINITY) {
						        return "Float.POSITIVE_INFINITY";
						      } else if (d == Float.NEGATIVE_INFINITY) {
							return "Float.NEGATIVE_INFINITY";
						      } else {
						        rep = d.toString();
						      }
						      assert rep != null;
						      rep = rep + "f";
						      if (rep.charAt(0) == '-')
						        rep = "(" + rep + ")";
						      return rep;

						    }else if (boolean.class.equals(valueClass)) {

						        // true and false are explicit enough; don't need cast.
						        return value.toString();

						      } else if (long.class.equals(valueClass)) {

						        String rep =value.toString() + "L";
						        if (rep.charAt(0) == '-')
						          rep = "(" + rep + ")";
						        return rep;

						      } else if (byte.class.equals(valueClass)) {

						        String rep =  value.toString();
						        if (rep.charAt(0) == '-')
						          rep = "(" + rep + ")";
						        rep =  rep;
						        return rep;

						      } else if (short.class.equals(valueClass)) {

						        String rep =  value.toString();
						        if (rep.charAt(0) == '-')
						          rep = "(" + rep + ")";
						        rep = rep;
						        return rep;

						      }

	    	 return(value.toString());
		 }
		 public  static Object mapValueToObject(Class<?> valType,String val) {
				Object obj = null;
				if(val==null||val.equals("null")||(!Filter.isPrimitiveOrStringType(valType)))
					return obj;
				try{
					if (valType ==  java.lang.String.class)
						obj = new java.lang.String(val.trim());
					else if (valType ==  java.lang.Integer.class||valType == int.class)
						obj = new Integer(Integer.parseInt(val.trim()));
					else if (valType ==  java.lang.Long.class||valType == Long.class)
						obj = new Long(Long.parseLong(val.trim()));
					else if (valType ==  java.lang.Float.class|| valType ==float.class)
						obj = new Float(Float.parseFloat(val.trim()));
					else if (valType ==  java.lang.Double.class|| valType ==double.class)
						obj = new Double(Double.parseDouble(val.trim()));
					else if (valType ==  java.lang.Boolean.class|| valType ==boolean.class)
						obj = new Boolean(Boolean.parseBoolean(val.trim()));
					else if (valType ==  java.lang.Character.class|| valType ==char.class)
						obj = new Character( (char) Integer.parseInt(val));
					else if (valType ==  java.lang.Byte.class||valType == byte.class)
						obj = new Byte(Byte.parseByte(val.trim()));
					else
						System.out.println("Error: ** ValueGenerator.mapValueToObject **"+ valType);
				}catch(Throwable e){
					System.out.println("Error: ** ValueGenerator.mapValueToObject **"+ valType);
					System.exit(1);
				}
				return obj;
			}
}